<template>
  <span v-if="props.showText">{{ message }}</span>
  <el-select
    v-else
    v-model="selectValue"
    filterable
    clearable
    popper-class="remote-select-box"
    :allow-create="allowCreate"
    :disabled="inputDisabled"
    :placeholder="calcPlaceholder"
    :filter-method="filterMethod"
    :popper-append-to-body="true"
    @compositionstart="compositionStart"
    @compositionend="compositionEnd"
    @change="handleChange"
    @clear="handleClear"
  >
    <template #empty>
      <span class="empty">{{ loading ? '数据加载中...' : tipText }}</span>
    </template>
    <span v-if="options.length === 0" class="empty">{{ loading ? '数据加载中...' : tipText }}</span>
    <span v-if="!loading && options.length > 0">
      <el-option
        v-for="(item, index) in options"
        :key="index"
        :label="(item.name || '').length > 30 ? ((item.name || '').substring(0, 30) + '...') : (item.name || '')"
        :title="(item.name || '').length > 30 ? (item.name || '') : ''"
        :value="item.id"
      ></el-option>
    </span>
    <div v-if="!loadEnd" style="width: 100%;text-align: center;padding-top: 5px">
      <el-button
        :disabled="loading"
        @click="loadPage"
        type="primary"
        link
      >{{ loading ? '正在加载中...' : '加载更多' }}</el-button>
    </div>
  </el-select>
</template>

<script setup>
import { ref, watch, onMounted, computed, onUnmounted } from 'vue';
import { useDisabled } from 'element-plus';
import api from '@/http/api';
import tool from "@/utils/tool";

const emit = defineEmits(['update:modelValue', 'change', 'blur', 'clear', 'update:name']);
const props = defineProps({
  modelValue: {
    type: String,
    default: ''
  },
  query: Object,
  option: Object,
  dataUrl: String,
  filterName: {
    type: String,
    default: 'name'
  },
  allowCreate: {
    type: Boolean,
    default: false
  },
  filterable: {
    type: Boolean,
    default: false
  },
  pageSize: {
    type: Number,
    default: 10,
  },
  placeholder: {
    type: String,
    default: '--请选择--'
  },
  idProp: {
    type: String,
    default: 'id'
  },
  nameProp: {
    type: String,
    default: 'name'
  },
  selectFirst: {
    type: Boolean,
    default: false
  },
  loadAll : {
    type: Boolean,
    default: false
  },
  showText: Boolean
});

const initializing = ref(true);
const loading = ref(false);
const tipText = ref('请输入内容搜索');
const timer = ref(0);
const options = ref([]);
const message = ref('');
const inputDisabled = useDisabled();
const selectValue = ref(props.modelValue || '');
const selectFirst = ref(false);
const pageNumber = ref(1);
const loadEnd = ref(false);

const calcPlaceholder = computed(() => {
  return inputDisabled.value ? ' ' : props.placeholder;
});

const filterValue = ref('');
const isComposing = ref(false);
const compositionStart = () => {
  isComposing.value = true;
};

const compositionEnd = () => {
  isComposing.value = false;
  filterMethod(filterValue.value);
};

const handleChange = () => {
  if (!selectValue.value) {
    changeInput('', '');
    return;
  }

  const currOpt = options.value.find(item => item.id === selectValue.value);
  if (currOpt) {
    changeInput(currOpt.id, currOpt.name);
  } else if (props.option && props.option.id === selectValue.value) {
    options.value.push(props.option);
  } else if (props.allowCreate) {
    const valueOpt = options.value.find(item => item.name === selectValue.value);
    if (!valueOpt) {
      options.value.push({id: selectValue.value, name: selectValue.value});
      changeInput(selectValue.value, selectValue.value);
    }
  }
};

const initSelectFirst = () => {
  if (!inputDisabled.value
      && props.selectFirst === true
      && selectFirst.value === false
      && (props.modelValue || '') === ''
      && options.value.length > 0) {
    selectFirst.value = true;
    changeInput(options.value[0].id, options.value[0].name);
  }
};

const changeInput = (id, name) => {
  emit('update:modelValue', id);
  emit('update:name', name);
  emit('change', {id: id, name: name});
  selectValue.value = id;
  message.value = name;
};

const handleClear = () => {
  pageNumber.value = 1;
  selectValue.value = '';
  oldValue.value = '';
  options.value = [];
  changeInput('', '');
  return true;
};

const oldValue = ref('');
const filterMethod = (value) => {
  filterValue.value = value;
  if (isComposing.value) return;
  if (initializing.value) return;
  if (oldValue.value === '' && options.value.length !== 0 && !value) return;

  pageNumber.value = 1;
  loadEnd.value = false;
  let params = {};
  params[props.filterName] = value;
  oldValue.value = value;
  loadData(params);
};

const loadPage = () => {
  let params = {};
  params[props.filterName] = oldValue.value;
  loadData(params);
};

const loadData = (params) => {
  if (loading.value) {
    if (timer.value) {
      clearTimeout(timer.value);
    }
    timer.value = setTimeout(() => loadData(params), 500);
  } else {
    if (timer.value) {
      clearTimeout(timer.value);
    }
    timer.value = null;
    doLoadData(params);
  }
};

const doLoadData = async(params) => {
  if (params == null) {
    params = { pageSize: props.pageSize };
  } else {
    params.pageSize = params.pageSize ? params.pageSize : props.pageSize
  }
  params = Object.assign({pageNumber: pageNumber.value}, params, props.query);
  loading.value = true;
  let option = options.value.find(item => item.id === selectValue.value) || null;

  try {
    let res = await api.post(props.dataUrl, params);
    if (res.data && res.data.length > 0) {
      if (res.data.length < props.pageSize) {
        loadEnd.value = true;
      }
      if (pageNumber.value === 1) {
        let arr = [];
        res.data.forEach((item) => {
          if ((item[props.idProp] || '') !== '') {
            const labelName = generateName(props.nameProp, item);
            arr.push({ id: item[props.idProp], name: labelName })
          }
        });

        if (option && !arr.some(item => item.id === option.id)) {
          arr.unshift(option);
        }
        options.value = arr;
        initSelectFirst();
        calcValue();
      } else {
        res.data.forEach((item) => {
          if ((item[props.idProp] || '') !== ''
              && !options.value.find(find => find.id === item[props.idProp])) {
            const labelName = generateName(props.nameProp, item);
            options.value.push({ id: item[props.idProp], name: labelName })
          }
        });
      }
    } else {
      options.value = [];
      tipText.value = '暂无数据';
    }
  } catch (e) {
    console.log(e);
  }

  pageNumber.value++;
  loading.value = false;
  timer.value = null;
};

const calcValue = () => {
  if (options.value == null || options.value.length <= 0) {
    selectValue.value = '';
    message.value = '';
  } else {
    let currOpt = options.value.find(item => item.id === selectValue.value);
    if (currOpt) {
      message.value = currOpt[props.nameProp];
    } else {
      selectValue.value = '';
      message.value = '';
    }
  }
};

const generateName = (name, json) => {
  return name.replace(/[0-9a-zA-Z_]+/g, function(s) {
    return json[s] || '';
  });
};

const init = async(data) => {
  selectValue.value = data.modelValue || '';
  if (initializing.value) {
    options.value = [];
    let option = data.option || {};
    if (option.id && option.name) {
      options.value.unshift(option);
      selectValue.value = option.id;
      message.value = option.name;
    }

    if (props.loadAll) {
      await doLoadData({pageSize: 100});
    }
  }
};

watch(() => tool.deepClone(props), (data, oldData) => {
  selectValue.value = data.modelValue || '';
  if (JSON.stringify(data.query) !== JSON.stringify(oldData.query)) {
    selectValue.value = '';
    data.modelValue = '';
    changeInput('', '');
    pageNumber.value = 1;
    options.value = [];
    doLoadData();
    return;
  }

  if (selectValue.value
      && props.option
      && props.option.id
      && props.option.name) {
    let find = options.value.find(item => item.id === props.option.id);
    if (!find) {
      options.value.unshift(props.option);
    }
  }
}, {deep: true});

onMounted(() => {
  initializing.value = true;
  init(props);
  initializing.value = false;
});

onUnmounted(() => {
  clearTimeout(timer.value);
  options.value = [];
  message.value = '';
});
</script>

<style>
.remote-select-box .el-select-dropdown__wrap {
  max-height: 70vh;
  text-align: left;
}
</style>
<style scoped>
.empty {
  font-size: 14px;
  padding: 0 20px;
  position: relative;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #606266;
  height: 40px;
  line-height: 40px;
  box-sizing: border-box;
}
</style>
